/*
Copyright (C) 2015 Marien Raat, Pieter Staal

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <sfmlFrontend.h>

using namespace sf;

SFMLFrontend::SFMLFrontend(unsigned screenSizeX, unsigned screenSizeY)
{
    screenSize = Vector2u(screenSizeX, screenSizeY);
    window = new RenderWindow(VideoMode(screenSize.x, screenSize.y), "Float");

#ifndef OS_WIN
    randomDevice = new std::random_device;
    mt = new std::mt19937((*randomDevice)());
#else
    mt = new std::mt19937(time(0));
#endif

    heightmapGenerator = new HeightmapGenerator(mt);

    mapDisplayer = new MapDisplayer(screenSize, MAP_DISPLAY_TYPE_HEIGHTMAP);

    markovNames = new MarkovNames("data/names.txt", 2, mt);
    mapDisplayer->setIslandName(markovNames->createName());

    landMapGenerator = new LandMapGenerator;
    waterLevel = 0.45;
    landMapGenerator->setWaterLevel(waterLevel);

    lightingGenerator = new LightingGenerator;
    useMouseLamp = false;
    mouseLampHeight = 1.0f * pow(10.0f, 3.0f);

    dsMapSettings.size = screenSize.x;
    dsMapSettings.h = 0.45;
    dsMapSettings.mapType = MAPTYPE_ISLAND;
    dsMapSettings.overwriteOldMap = true;
    dsMapSettings.modifierFunction = customDisplacementModifier;
    heightmapGenerator->createNewMap(dsMapSettings);

    mapDisplayer->setHeightmap(heightmapGenerator->map);
}

void SFMLFrontend::start()
{
    loadFiles();

    while (window->isOpen()) {
	handleEvents();
	if (focus)
	    handleInput();
	update();
	draw();
    }
    return;
}

void SFMLFrontend::loadFiles()
{
    if (!islandNameFont.loadFromFile("fonts/DejaVuSerif.ttf"))
	std::cout << "File \"fonts/DejaVuSerif.ttf\" not found!" << std::endl
	<< "Check if you are running the program from the main project folder."
	<< std::endl;
    else
	mapDisplayer->setNamesFont(&islandNameFont);
}

void SFMLFrontend::handleEvents()
{
    Event event;
    // Only handle one of the KeyPressed events per cycle to prevent the events
    // of accumilating when a key is kept pressed.
    bool handledKeyPressedEvent = false;
    while (window->pollEvent(event)) {
	if (event.type == Event::Closed) {
	    window->close();
	}
	if (event.type == Event::KeyPressed && !handledKeyPressedEvent) {
	    handledKeyPressedEvent = true;
	    if (event.key.code == Keyboard::Escape) {
		window->close();
	    }
	    if (event.key.code == Keyboard::N) {
		heightmapGenerator->createNewMap(dsMapSettings);
		mapDisplayer->setIslandName(markovNames->createName());
		updateHeightmapDisplay();
	    }
	    if (event.key.code == Keyboard::P) {
		heightmapGenerator->pass();
		updateHeightmapDisplay();
	    }
	    if (event.key.code == Keyboard::F) {
		heightmapGenerator->finishMap();
		updateHeightmapDisplay();
	    }
	    if (event.key.code == Keyboard::C) {
		if (heightmapGenerator->isMapDone()) {
		    heightmapGenerator->createNewMap(dsMapSettings);
		    mapDisplayer->setIslandName(markovNames->createName());
		}
		heightmapGenerator->finishMap();
		updateHeightmapDisplay();
	    }
	    if (event.key.code == Keyboard::I) {
		mapDisplayer->setSmoothingType(SMOOTHING_TYPE_INTERPOLATE);
		updateHeightmapDisplay();
	    }
	    if (event.key.code == Keyboard::A) {
		mapDisplayer->setSmoothingType(SMOOTHING_TYPE_AVERAGE);
		updateHeightmapDisplay();
	    }
	    if (event.key.code == Keyboard::Num1) {
		mapDisplayer->setMapDisplayType(MAP_DISPLAY_TYPE_HEIGHTMAP);
		updateHeightmapDisplay();
	    }
	    if (event.key.code == Keyboard::Num2) {
		mapDisplayer->setMapDisplayType(MAP_DISPLAY_TYPE_SIMPLE_MAP);
		updateHeightmapDisplay();
	    }
            if (event.key.code == Keyboard::Num3) {
                mapDisplayer->setMapDisplayType(MAP_DISPLAY_TYPE_LAND_MAP);
                updateHeightmapDisplay();
            }
            if (event.key.code == Keyboard::Num4) {
                mapDisplayer->setMapDisplayType(MAP_DISPLAY_TYPE_LIGHTED_MAP);
                updateHeightmapDisplay();
            }
            if (event.key.code == Keyboard::S) {
                // Write the contents of the current screen to "screenshot.png"
                Image screen = window->capture();
                screen.saveToFile("screenshot.png");
            }
            if (event.key.code == Keyboard::M &&
                mapDisplayer->getMapDisplayType()
                == MAP_DISPLAY_TYPE_LIGHTED_MAP) {
                while (Keyboard::isKeyPressed(Keyboard::M));
                useMouseLamp = !useMouseLamp;
                updateHeightmapDisplay();
            }
        }

        if (event.type == Event::MouseWheelMoved &&
            mapDisplayer->getMapDisplayType() ==
            MAP_DISPLAY_TYPE_LIGHTED_MAP) {
            double delta = (double) event.mouseWheel.delta;
            mouseLampHeight *= pow(2.0, 0.1 * delta);
        }

	// Focus control
	if (event.type == Event::LostFocus)
	    focus = false;
	if (event.type == Event::GainedFocus)
	    focus = true;
    }
}

void SFMLFrontend::handleInput()
{

}

void SFMLFrontend::updateHeightmapDisplay()
{
    mapDisplayer->setHeightmapPassSize
	(heightmapGenerator->currentPassSettings
	 .passSize);
    if (mapDisplayer->getMapDisplayType() == MAP_DISPLAY_TYPE_LAND_MAP) {
        mapDisplayer->updateHeightmapDisplay();
        mapDisplayer->setLandMap
            (landMapGenerator->generateLandMap
             (mapDisplayer->normalizedHeightmap,
              screenSize.x, screenSize.y, true));
    }
    if (mapDisplayer->getMapDisplayType()
        == MAP_DISPLAY_TYPE_LIGHTED_MAP) {
        mapDisplayer->updateHeightmapDisplay();
        double *cHeightmap = getClippedHeightmap(waterLevel);
        lightingGenerator->generateNormalMap(cHeightmap,
                                             screenSize.x,
                                             screenSize.y);
        delete cHeightmap;

        Light sun;
        float sunStrength = 0.6f;
        sun.color = {1.0f * sunStrength, 1.0f * sunStrength,
                     0.6f * sunStrength};
        sun.falloff = {0.0f, 0.0f, 0.0f};
        sun.position = {-1.0f * pow(10.0f, 3.0f), -1.0f * pow(10.0f, 3.0f),
                        1 * pow(10.0f, 4.0f)};

        Light mouseLamp;
        float mouseLampStrength = 0.6f;
        mouseLamp.color = {1.0f * mouseLampStrength,
                           1.0f * mouseLampStrength,
                           1.0f * mouseLampStrength};
        mouseLamp.falloff = {0.0f, 0.0f, 0.0f};

        Vector2i mousePosition = Mouse::getPosition(*window);
        mouseLamp.position = {(float) mousePosition.x,
                              (float) mousePosition.y,
                              (float) mouseLampHeight};

        std::vector<Light> lights;

        if (useMouseLamp)
            lights.push_back(mouseLamp);
        else
            lights.push_back(sun);

        float atmosphereStrength = 0.4f;
        Vec3f diffuse = {0.8f * atmosphereStrength,
                         0.8f * atmosphereStrength,
                         1.0f * atmosphereStrength};

        lightingGenerator->generateLightMap(lights, diffuse);

        mapDisplayer->setLightMap(lightingGenerator->lightmap);
    }
    mapDisplayer->updateDisplay();
}

void SFMLFrontend::update()
{
    if (mapDisplayer->getMapDisplayType()
        == MAP_DISPLAY_TYPE_LIGHTED_MAP &&
        useMouseLamp)
        updateHeightmapDisplay();
}

void SFMLFrontend::draw()
{
    window->clear();
    mapDisplayer->draw(window);
    window->display();
}

double *SFMLFrontend::getClippedHeightmap(double clipValue)
{
    double *clippedHeightmap = new double[screenSize.x * screenSize.y];
    for (int i = 0; i < screenSize.x * screenSize.y; i++)
        clippedHeightmap[i] = std::max(clipValue,
                                       mapDisplayer->normalizedHeightmap[i]);
    return clippedHeightmap;
}
